home *** CD-ROM | disk | FTP | other *** search
/ MacTech 1 to 12 / MacTech-vol-1-12.toast / Source / MacTech® Magazine / Volume 06 - 1990 / 06.10 Oct 90 / LZW Source / LComp.p next >
Encoding:
Text File  |  1989-05-07  |  16.3 KB  |  597 lines  |  [TEXT/MPS ]

  1. {$R-}
  2. {$D+}
  3. {$DEFC DEBUG}
  4. {$SETC DEBUG=TRUE}
  5. PROGRAM LComp;
  6.  
  7. { Simple case LZW compression }
  8.  
  9. USES
  10.     MemTypes,
  11.     QuickDraw,
  12.     OSIntf,
  13.     ToolIntf,
  14.     PackIntf;
  15.     
  16. CONST
  17.     maxBuff = 8192;        {i/o buffer size}
  18.     maxTab = 16383;        {Table size minus 1 ($3FFF)}
  19.     noPrev = $7FFF;
  20.     eofChar = -2;
  21.     endList = -1;
  22.     empty = -3;
  23.     clearCode = 256;    {Reserved code to signal adaptive reset ($100) }
  24.     checkGap = 10000;    {How frequently do we check for adaptive?}
  25.     
  26. TYPE
  27.     StringTableEntry = RECORD
  28.         prevByte: Integer;
  29.         follByte: Integer;
  30.         next: Integer;
  31.         used: Boolean;
  32.         reserved: Boolean;
  33.     END;
  34.     StringTableArray = ARRAY [0..maxTab] OF StringTableEntry; {128K structure unless packed}
  35.     StringTablePtr = ^StringTableArray;
  36.     IntPtr = ^Integer;
  37.     Buffer = PACKED ARRAY [1..maxBuff] OF Char;
  38.     BufPtr = ^Buffer;
  39.     HeaderRecord = RECORD
  40.         name: String[31];
  41.         dfSize: LongInt;
  42.         rfSize: LongInt;
  43.         fndrInfo: FInfo;
  44.     END;
  45.     Remainder = (none, sixBit, fourBit, twoBit);
  46.  
  47. VAR
  48.     inRef: Integer;                    {Reference number of input file}
  49.     outRef: Integer;                {Reference number of output file}
  50.     inVRefNum: Integer;            {Volume/WD reference number of input file}
  51.     outVRefNum: Integer;        {Volume/WD reference number of output file}
  52.     eofSignal: Boolean;            {Flag that it's time to clean up}
  53.     inBufSize: LongInt;            {Count of characters in the input buffer }
  54.     inputPos: Integer;            {Position in input buffer}
  55.     outputPos: Integer;            {Position in output buffer}
  56.     bytesRead: LongInt;            {Total bytes read from input file}
  57.     bytesWritten: LongInt;    {Total bytes written to output file}
  58.     ratio: Extended;                {Compression ratio (bytesRead/bytesWritten)}
  59.     checkPoint: LongInt;        {Next time we check to see whether table adaptation necessary}
  60.     
  61.     inputBuffer: BufPtr;        {Dynamically allocated data storage}
  62.     outputBuffer: BufPtr;        { " }
  63.     
  64.     stringTable: StringTablePtr;
  65.     infileName:    Str255;            {Name of the file we're compressing}
  66.     tableUsed: Integer;            {Number of entries currently in string table}
  67.     outputCode: Integer;        {Code (14-bit) that we're going to output}
  68.     carryOver: Remainder;        {How many bits we have in the code we're building}
  69.     doingDFork: Boolean;        {Flag that tells which fork of the file we're compressing}
  70.     fsErr: OSErr;                        {Result of last file system call}
  71.     dataForkSize: LongInt;    {Number of bytes in data fork}
  72.     rsrcForkSize: LongInt;    {Number of bytes in resource fork}
  73.     progWindow: WindowPtr;    {Window where we display progress}
  74.     boundsRect: Rect;                {Bounding rect of the progress window}
  75.     hdrRec: HeaderRecord;        {File information so that decompress will get things right}
  76.     resetCode: Integer;            {This is the hashCode for clearCode}
  77.     
  78.     
  79.     PROCEDURE _DataInit; EXTERNAL;    {MPW specific}
  80.     
  81.     
  82.     PROCEDURE FileAlert(str: Str255);
  83.     
  84.     CONST
  85.         fsAlert =    1111;
  86.     
  87.     VAR
  88.         item: Integer;
  89.     
  90.     BEGIN
  91.         ParamText(str, '', '', '');
  92.         item := StopAlert(fsAlert, NIL);
  93.         fsErr := FSClose(inRef);
  94.         fsErr := FSClose(outRef);
  95.         fsErr := FlushVol(NIL, outVRefnum);
  96.     END {FileAlert} ;
  97.     
  98.     
  99. {$IFC DEBUG}
  100.     PROCEDURE DebugAlert(l1, l2: LongInt);
  101.     
  102.     CONST
  103.         dbgAlert = 1112;
  104.     
  105.     VAR
  106.         s1, s2: Str255;
  107.         item: Integer;
  108.     
  109.     BEGIN
  110.         NumToString(l1, s1);
  111.         NumToString(l2, s2);
  112.         ParamText(s1, s2, '', '');
  113.         item := NoteAlert(dbgAlert, NIL);
  114.     END {DebugAlert} ;
  115. {$ENDC}
  116.  
  117.     PROCEDURE ShowProgress;
  118.     
  119.     VAR
  120.         savePort: GrafPtr;
  121.         aStr: Str255;
  122.     
  123.     BEGIN
  124.         GetPort(savePort);
  125.         SetPort(progWindow);
  126.         EraseRect(progWindow^.portRect);
  127.         NumToString(bytesWritten, aStr);
  128.         MoveTo(5, 10);
  129.         DrawString(aStr);
  130.         NumToString(bytesRead, aStr);
  131.         MoveTo(5, 25);
  132.         DrawString(aStr);
  133.         NumToString(tableUsed, aStr);
  134.         MoveTo(5, 40);
  135.         DrawString(aStr);
  136.         SetPort(savePort);
  137.     END {ShowProgress} ;
  138.     
  139.     
  140.     FUNCTION HashIt(prevC, follC: Integer): Integer;
  141.     {"Dumb" hash routine, must match the routine in decompress}
  142.     
  143.     VAR
  144.         temp,
  145.         local: LongInt;
  146.     
  147.     BEGIN
  148.         {Possible alternative commented out below}
  149. {        local := BOR((prevC+follC), $00008000);
  150.         temp := local * local;
  151.         local := BAND(BSR(temp, 7), maxTab);    }
  152.         
  153.         HashIt := BAND(BXOR(BSL(prevC, 5), follC), maxTab);
  154.     END {HashIt} ;
  155.     
  156.     
  157.     FUNCTION GetHashCode(prevC, follC: Integer): Integer;
  158.     {    Return value is the hash code for <w>c string }
  159.     
  160.     VAR
  161.         index: Integer;
  162.         index2: Integer;
  163.     
  164.     BEGIN
  165.         index := HashIt(prevC, follC);
  166.         
  167.         {If the entry isn't already used we have a hash code}
  168.         IF (stringTable^[index].used) THEN BEGIN
  169.             {Entry already used, skip to end of collision list}
  170.             WHILE stringTable^[index].next <> endList DO
  171.                 index := stringTable^[index].next;
  172.             {Begin a linear probe down a bit from last entry in the collision list}
  173.             index2 := BAND(index + 101, maxTab);
  174.             {Look for an unused entry using linear probing}
  175.             WHILE stringTable^[index2].used DO
  176.                 index2 := BAND(Succ(index2), maxTab);
  177.             {Point the previous end of collision list at this new node}
  178.             stringTable^[index].next := index2;
  179.             GetHashCode := index2;
  180.         END ELSE GetHashCode := index;
  181.     END {GetHashCode} ;
  182.     
  183.     
  184.     PROCEDURE MakeTableEntry(prevC, follC: Integer);
  185.     
  186.     VAR
  187.         aCode: Integer;
  188.     
  189.     BEGIN
  190.         IF tableUsed <= maxTab THEN BEGIN
  191.             aCode := GetHashCode(prevC, follC);
  192.             WITH stringTable^[aCode] DO BEGIN
  193.                 used := true;
  194.                 next := endList;
  195.                 prevByte := prevC;
  196.                 follByte := follC;
  197.             END;
  198.             
  199.             tableUsed := tableUsed + 1;
  200.         END;
  201.     END {MakeTableEntry} ;
  202.     
  203.     
  204.     FUNCTION LookupString(prevC, follC: Integer): Integer;
  205.     
  206.     VAR
  207.         index: Integer;
  208.         found: Boolean;
  209.         myEntry: StringTableEntry;
  210.     
  211.     BEGIN
  212.         index := HashIt(prevC, follC);
  213.         LookupString := endList;
  214.         found := FALSE;
  215.         {    Search list of collision entries for one that matches <w>c }
  216.         REPEAT
  217.             myEntry := stringTable^[index];
  218.             IF (myEntry.prevByte = prevC) &
  219.                  (myEntry.follByte = follC) THEN found := true
  220.             ELSE index := myEntry.next;
  221.         UNTIL found OR (index = endList);
  222.         { Return index if <w>c found, endList otherwise }
  223.         IF found THEN LookupString := index;
  224.     END {LookupString} ;
  225.     
  226.     
  227.     PROCEDURE GetChar(VAR c: Integer);
  228.     { Read a character from the input file.  If the input file is the data fork
  229.         and at the end.  Close it and open the resource fork, inputting from it. }
  230.     
  231.     VAR
  232.         logEOF: LongInt;
  233.     
  234.     BEGIN
  235.         inputPos := inputPos + 1;
  236.         IF inputPos > inBufSize THEN BEGIN
  237.             inBufSize := maxBuff;
  238.             fsErr := FSRead(inRef, inBufSize, Ptr(inputBuffer));
  239.             inputPos := 1;
  240.         END;
  241.         IF inBufSize = 0 THEN BEGIN {We're in a possible eof situation}
  242.             IF doingDFork THEN BEGIN {Check for the resource fork}
  243.                 doingDFork := false;
  244.                 fsErr := FSClose(inRef);
  245.                 fsErr := OpenRF(infileName, inVRefnum, inRef);
  246.                 IF fsErr = noErr THEN BEGIN
  247.                     fsErr := GetEOF(inRef, logEOF);
  248.                     rsrcForkSize := logEOF;
  249.                     hdrRec.rfSize := logEOF;
  250.                     fsErr := SetFPos(inRef, fsFromStart, 0);
  251.                     inputPos := 1;
  252.                     inBufSize := maxBuff;
  253.                     fsErr := FSRead(inRef, inBufSize, Ptr(inputBuffer));
  254.                     IF inBufSize = 0 THEN BEGIN {Empty resource fork}
  255.                         c := eofChar;
  256.                         eofSignal := true;
  257.                     END ELSE BEGIN
  258.                         c := Ord(inputBuffer^[inputPos]);
  259.                         bytesRead := bytesRead + 1;
  260.                     END;
  261.                 END ELSE BEGIN    {No resource fork, we're done!}
  262.                     rsrcForkSize := 0;
  263.                     hdrRec.rfSize := 0;
  264.                     eofSignal := true;
  265.                     c := eofChar;
  266.                     Exit(GetChar);
  267.                 END;
  268.             END ELSE BEGIN    {We are done, eof has been reached!}
  269.                 eofSignal := true;
  270.                 c := eofChar;
  271.             END;
  272.         END ELSE BEGIN
  273.             c := Ord(inputBuffer^[inputPos]);
  274.             bytesRead := bytesRead + 1;
  275.         END;
  276.     END {GetChar} ;
  277.     
  278.     
  279.     PROCEDURE PutChar(c: Integer);
  280.     
  281.     VAR
  282.         count: LongInt;
  283.     
  284.     BEGIN
  285.         IF outputPos >= maxBuff THEN BEGIN
  286.             count := maxBuff;
  287.             fsErr := FSWrite(outRef, count, Ptr(outputBuffer));
  288.             IF fsErr <> noErr THEN FileAlert('Write error in PutChar');
  289.             outputPos := 0;
  290.             ShowProgress;
  291.         END;
  292.         outputPos := outputPos + 1;
  293.         bytesWritten := bytesWritten + 1;
  294.         outputBuffer^[outputPos] := Chr(c);
  295.     END {PutChar} ;
  296.     
  297.     
  298.     PROCEDURE InitStrTable;
  299.     
  300.     VAR
  301.         i: Integer;
  302.     
  303.     BEGIN
  304.         tableUsed := 0;
  305.         FOR i := 0 TO maxTab DO BEGIN
  306.             WITH stringTable^[i] DO BEGIN
  307.                 prevByte := noPrev;
  308.                 follByte := noPrev;
  309.                 next := -1;
  310.                 used := false;
  311.                 reserved := false;
  312.             END;
  313.         END;
  314.         {Enter all single ascii characters into the string table}
  315.         FOR i := 0 TO clearCode DO
  316.             MakeTableEntry(noPrev, i);
  317.     END {InitStrTable} ;
  318.     
  319.     
  320.     PROCEDURE Initialize;
  321.     
  322.         PROCEDURE InitManagers;
  323.         
  324.         BEGIN
  325.             MaxApplZone;
  326.             InitGraf(@thePort);
  327.             InitFonts;
  328.             FlushEvents(everyEvent, 0);
  329.             InitWindows;
  330.             InitMenus;
  331.             TEInit;
  332.             InitDialogs(NIL);
  333.             InitCursor;
  334.             UnLoadSeg(@_DataInit);    {MPW-specific unload}
  335.         END {InitManagers} ;
  336.     
  337.     BEGIN
  338.         InitManagers;
  339.         
  340.         inputBuffer := BufPtr(NewPtr(SizeOf(Buffer)));
  341.         outputBuffer := BufPtr(NewPtr(SizeOf(Buffer)));
  342.         stringTable := StringTablePtr(NewPtr(SizeOf(StringTableArray)));
  343.         
  344.         inBufSize := 0;
  345.         inputPos := 1;    {With inBufSize set to zero this will force the 1st read}
  346.         outputPos := 0;
  347.         bytesRead := 0;
  348.         bytesWritten := 0;
  349.         doingDFork := true;
  350.         outputCode := empty;
  351.         carryOver := none;
  352.         dataForkSize := 0;
  353.         rsrcForkSize := 0;
  354.         ratio := 0.0;
  355.         checkPoint := checkGap;
  356.         
  357.         InitStrTable;
  358.         resetCode := LookupString(noPrev, clearCode);
  359.     END {Initialize} ;
  360.     
  361.     
  362.     PROCEDURE GetTopLeft({using} dlogID: Integer;
  363.                                              {returning} VAR where: Point);
  364.     {    —    Return the point where DLOG(dlogID) should have its top-left corner so as
  365.         —    to be centered in the area below the menubar of the main screen.  The
  366.         —    centering is horizontal, vertically it should be one-third of the way.  This
  367.         —    is achieved by getting the DLOG resource and centering its rectangle within
  368.         —    screenBits.bounds after adjusting screenBits.bounds by mBarHeight. }
  369.     
  370.     CONST
  371.         mBarHeight = $0BAA;    {Address of global integer containing menu bar height}
  372.     
  373.     VAR
  374.         screenRect,
  375.         dlogRect:    Rect;
  376.         mBarAdjustment: IntPtr;
  377.         aDlog: DialogTHndl;
  378.     
  379.     BEGIN
  380.         screenRect := screenBits.bounds;
  381.         mBarAdjustment := IntPtr(mBarHeight);
  382.         screenRect.top := screenRect.top + mBarAdjustment^;
  383.         aDlog := DialogTHndl(GetResource('DLOG', dlogID));
  384.         DetachResource(Handle(aDlog));
  385.         dlogRect := aDlog^^.boundsRect;
  386.         WITH screenRect DO BEGIN
  387.             where.v := ((bottom - top) - (dlogRect.bottom - dlogRect.top)) DIV 3;
  388.             where.h := ((right - left) - (dlogRect.right - dlogRect.left)) DIV 2;
  389.         END;
  390.     END {GetTopLeft};
  391.     
  392.  
  393.     FUNCTION GetInputFile({returning} VAR refNum: Integer): Boolean;
  394.     { — Return false if the user cancels the request, true otherwise.  If a file
  395.         —    is selected for compression, open the file and pass back the refnum.
  396.         —    The constant getDlgID is from PackIntf.
  397.         —    Global side-effects of this routine include the initialization of a number
  398.         —    of fields of the hdrRec global and the setting of the inVRefNum global.}
  399.     
  400.     CONST
  401.         allFiles = -1;
  402.         
  403.     VAR
  404.         tl: Point;
  405.         reply: SFReply;
  406.         typeList: SFTypeList;
  407.         anErr,
  408.         error: OSErr;
  409.         finderInfo: FInfo;
  410.         logEOF: LongInt;
  411.         dtRec: DateTimeRec;
  412.     
  413.     BEGIN
  414.         GetTopLeft(getDlgID, tl);
  415.         {typeList doesn't need to be initialized since we're asking for all files with the -1}
  416.         SFGetFile(tl, '', NIL, allFiles, typeList, NIL, reply);
  417.         IF reply.good THEN BEGIN
  418.             error := FSOpen(reply.fName, reply.vRefnum, refNum);
  419.             inVRefNum := reply.vRefnum;
  420.             IF error = noErr THEN error := SetFPos(refNum, fsFromStart, 0)
  421.             ELSE anErr := FSClose(refNum);
  422.             IF error = noErr THEN BEGIN
  423.                 GetInputFile := true;
  424.                 infileName := reply.fName;
  425.                 anErr := GetEOF(refNum, logEOF);
  426.                 dataForkSize := logEOF;
  427.                 rsrcForkSize := 0;    {for the moment -- corrected when the resource fork is opened}
  428.                 hdrRec.name := infileName;
  429.                 hdrRec.dfSize := dataForkSize;
  430.                 anErr := GetFInfo(reply.fName, inVRefnum, finderInfo);
  431.                 hdrRec.fndrInfo := finderInfo;
  432.             END ELSE GetInputFile := false;
  433.         END ELSE GetInputFile := false;
  434.     END {GetInputFile} ;
  435.     
  436.     
  437.     FUNCTION GetOutputFile({returning} VAR refNum: Integer): Boolean;
  438.     
  439.     VAR
  440.         tl: Point;
  441.         reply: SFReply;
  442.         error: OSErr;
  443.         count: LongInt;
  444.     
  445.     BEGIN
  446.         GetTopLeft(putDlgID, tl);
  447.         SFPutFile(tl, '', '', NIL, reply);
  448.         IF reply.good THEN BEGIN
  449.             error := FSOpen(reply.fName, reply.vRefnum, refNum);
  450.             IF error <> noErr THEN BEGIN    {File didn't already exist, need to create it}
  451.                 error := Create(reply.fName, reply.vRefnum, 'LZWC', 'DATA');
  452.                 IF error = noErr THEN error := FSOpen(reply.fName, reply.vRefnum, refNum);
  453.                 IF error = noErr THEN BEGIN
  454.                     error := SetFPos(refNum, fsFromStart, 0);
  455.                     count := SizeOf(HeaderRecord);
  456.                     error := FSWrite(refNum, count, @hdrRec);
  457.                 END ELSE error := FSClose(refNum);
  458.             END;
  459.             IF error = noErr THEN BEGIN
  460.                 GetOutputFile := true;
  461.                 outVRefNum := reply.vRefnum;
  462.             END ELSE GetOutputFile := false;
  463.         END ELSE GetOutputFile := false;
  464.     END {GetOutputFile} ;
  465.     
  466.  
  467.     PROCEDURE Terminate;
  468.     
  469.     VAR
  470.         count: LongInt;
  471.     
  472.     BEGIN
  473.         ShowProgress;
  474.         count := outputPos;
  475.         fsErr := FSWrite(outRef, count, Ptr(outputBuffer));
  476.         IF fsErr = noErr THEN BEGIN
  477.             fsErr := SetEOF(outRef, bytesWritten+SizeOf(HeaderRecord));
  478.             IF fsErr = noErr THEN BEGIN
  479.                 fsErr := SetFPos(outRef, fsFromStart, 0);
  480.                 IF fsErr = noErr THEN BEGIN
  481.                     count := SizeOf(HeaderRecord);
  482.                     fsErr := FSWrite(outRef, count, @hdrRec);
  483.                     IF (fsErr <> noErr) | (count <> SizeOf(hdrRec)) THEN
  484.                         FileAlert('Header update error in Terminate');
  485.                 END ELSE FileAlert('Positioning error in Terminate');
  486.                 fsErr := FSClose(outRef);
  487.                 fsErr := FSClose(inRef);
  488.                 fsErr := FlushVol(NIL, outVRefNum);
  489.             END ELSE FileAlert('SetEOF Error in Terminate');
  490.         END ELSE FileAlert('Write Error in Terminate');
  491.     END {Terminate} ;
  492.     
  493.     
  494.     PROCEDURE PutCode(hashCode: Integer);
  495.     {    If the output code word is empty, then put out the first 8 bits of the
  496.         compression code and save the last six bits for the next time through.
  497.         If it's not empty, then put out the (saved) n bits from above prepended
  498.         to the first 8-n bits of the new code.  Then put out the last eight
  499.         bits of this code. }
  500.     
  501.     BEGIN
  502.         IF carryOver = none THEN BEGIN
  503.             PutChar(BAND(BSR(hashCode, 6), $00FF));        {most significant 8 bits}
  504.             outputCode := BAND(hashCode, $003F);            {save 6 lsb for next time}
  505.             carryOver := sixBit;
  506.         END ELSE IF carryOver = twoBit THEN BEGIN
  507.             PutChar(BAND(BSL(outputCode, 6), $00C0) +
  508.                             BAND(BSR(hashCode, 8), $003F));        {leftover 2 + first 6}
  509.             PutChar(BAND(hashCode, $00FF));                        {least significant 8 bits}
  510.             outputCode := empty;                                            {nothing left}
  511.             carryOver := none;
  512.         END ELSE IF carryOver = fourBit THEN BEGIN
  513.             PutChar(BAND(BSL(outputCode, 4), $00F0) +
  514.                             BAND(BSR(hashCode, 10), $000F));    {leftover 4 + 4 msbits}
  515.             PutChar(BAND(BSR(hashCode, 2), $00FF));        {next 8 bits}
  516.             outputCode := BAND(hashCode, $0003);            {save these two bits}
  517.             carryOver := twoBit;
  518.         END ELSE IF carryOver = sixBit THEN BEGIN
  519.             PutChar(BAND(BSL(outputCode, 2), $00FC) +
  520.                             BAND(BSR(hashCode, 12), $0003));    {leftover 6 + first 2 bits}
  521.             PutChar(BAND(BSR(hashCode, 4), $00FF));        {next 8 bits}
  522.             outputCode := BAND(hashCode, $000F);            {four bits left}
  523.             carryOver := fourBit;
  524.         END;
  525.     END {PutCode} ;
  526.     
  527.     
  528.     PROCEDURE CheckReset;
  529.     {    -- CheckReset tests the compression ratio to guarantee that it is monotonic
  530.         -- increasing.  It modifies the global variables ratio and checkPoint.  If
  531.         -- the compression ratio has decreased since the last checkPoint, the string
  532.         -- table is reinitialized, the code for a clearCode is issued to the output,
  533.         -- and ratio is reset to zero. }
  534.     
  535.     VAR
  536.         e1, e2, temp: Extended;
  537.     
  538.     BEGIN
  539.         {    Set the next checkPoint for checkGap from now }
  540.         checkPoint := bytesRead + checkGap;
  541.         e1 := bytesRead;
  542.         e2 := bytesWritten;
  543.         temp := e1 / e2;
  544.         IF temp >= ratio THEN ratio := temp
  545.         ELSE BEGIN
  546.             ratio := 0.0;
  547.             InitStrTable;
  548.             PutCode(resetCode);
  549.         END;
  550.     END {CheckReset} ;
  551.     
  552.  
  553.     PROCEDURE DoCompression;
  554.     
  555.     VAR
  556.         c: Integer;
  557.         w: Integer;
  558.         wc: Integer;
  559.         anEvent: EventRecord;
  560.     
  561.     BEGIN
  562.         GetChar(c);
  563.         w := LookupString(noPrev, c);
  564.         GetChar(c);
  565.         WHILE c <> eofChar DO BEGIN
  566.             wc := LookupString(w, c);
  567.             IF (wc = endList) THEN BEGIN
  568.                 PutCode(w);
  569.                 IF GetNextEvent(everyEvent, anEvent) THEN ;
  570.                 IF tableUsed <= maxTab THEN MakeTableEntry(w, c)
  571.                 ELSE IF bytesRead >= checkPoint THEN CheckReset;
  572.                 w := LookupString(noPrev, c)
  573.             END ELSE w := wc;
  574.             GetChar(c);
  575.         END;
  576.         PutCode(w);
  577.         
  578.         {Flush any remaining partial code to disk}
  579.         IF carryOver = sixBit THEN PutChar(BAND(BSL(outputCode, 2), $00FC))
  580.         ELSE IF carryOver = fourBit THEN PutChar(BAND(BSL(outputCode, 4), $00F0))
  581.         ELSE IF carryOver = twoBit THEN PutChar(BAND(BSL(outputCode, 6), $00C0));
  582.     END {DoCompression} ;
  583.  
  584. BEGIN
  585.     Initialize;
  586.     IF GetInputFile(inRef) THEN
  587.         IF GetOutputFile(outRef) THEN BEGIN
  588.             SetRect(boundsRect, 100, 50, 250, 100);
  589.             progWindow := NewWindow(NIL, boundsRect, 'Bytes Read',
  590.                     true, noGrowDocProc, Pointer(-1), false, 0);
  591.             DoCompression;
  592.             Terminate;
  593. {$IFC DEBUG}
  594.             DebugAlert(bytesRead, bytesWritten);
  595. {$ENDC}
  596.         END;
  597. END.